package com.example.addwater.service;

import com.alibaba.fastjson.JSON;
import com.drew.imaging.FileType;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ChumML created by 2020/12/02 8:38
 */

@Slf4j
@Service
public class WaterService {

    /**
     * 添加背景水印
     *
     * @param target
     *
     * @return
     *
     * @throws IOException
     */
    public InputStream addTextMarkWater(MultipartFile target) throws IOException {
        return pressTextAndImage ( target, Arrays.asList ( "此扫描件作为客户确认和内部管理所用", "不具有任何法律效力" ), "黑体", Font.ITALIC, 20, 0.1f, -45, Objects.requireNonNull ( target.getOriginalFilename () ).substring ( target.getOriginalFilename ().lastIndexOf ( "." ) + 1 ) );
    }

    /**
     * 添加前置水印
     *
     * @param target
     * @param name
     * @param address
     *
     * @return
     *
     * @throws Exception
     */
    public InputStream addImageAndTextMarkWater(MultipartFile target, String name, String address) throws Exception {
        return newAddWaterMarkByText ( target, name, address );
    }

    /**
     * 添加文字背景水印
     *
     * @param targetImg 目标图片流
     * @param pressText 水印文字
     * @param fontName  字体名称，    如：宋体
     * @param fontStyle 字体样式，如：粗体和斜体(Font.BOLD|Font.ITALIC)
     * @param fontSize  字体大小，单位为像素
     * @param alpha     透明度(0.0 -- 1.0, 0.0为完全透明，1.0为完全不透明)
     * @param degree    水印图片旋转角度
     *
     * @description 坐标(0, 0)在图片的左上角，(负数，负数)在中心位置，(0,xxx)在左边，(x,0)在最上边，其他位置根据图片来确定
     */
    private InputStream pressTextAndImage(MultipartFile targetImg, List<String> pressText, String fontName, int fontStyle, int fontSize, float alpha, Integer degree, String suffix) {
        ByteArrayOutputStream bs = null;
        ImageOutputStream imOut;
        try {
            // 检测图片是否被旋转
            int radian = checkRotate ( targetImg.getInputStream (), Objects.requireNonNull ( targetImg.getContentType () ) );
            BufferedImage targetImage = ImageIO.read ( targetImg.getInputStream () );
            // 获取正面图片
            targetImage = Rotate ( targetImage, radian );
            // 原始图片宽高
            int width = targetImage.getWidth ( null );
            int height = targetImage.getHeight ( null );
            // 画布
            BufferedImage bufferedImage = new BufferedImage ( width, height, BufferedImage.TYPE_INT_RGB );
            Graphics2D g = bufferedImage.createGraphics ();
            // 消除画图锯齿
            g.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT );
            // 画布上目标图
            g.drawImage ( targetImage.getScaledInstance ( width, height, Image.SCALE_SMOOTH ), 0, 0, null );
            g.setColor ( new Color ( 0 ) );
            // 设置渲染方式
            g.setComposite ( AlphaComposite.getInstance ( AlphaComposite.SRC_ATOP, alpha ) );
            // 设置水印旋转
            if (null != degree) {
                g.rotate ( Math.toRadians ( degree ) );
            }
            //获取文字所占的像素
            FontRenderContext context = g.getFontRenderContext ();
            //文字水印
            Font font = new Font ( fontName, fontStyle, fontSize );
            g.setFont ( font );
            Rectangle2D stringBounds = font.getStringBounds ( pressText.get ( 0 ) + pressText.get ( 1 ), context );
            // 消除文字锯齿
            g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
            // 文字宽高
            final int textWidth = (int) stringBounds.getWidth ();
            final int textHeight = (int) stringBounds.getHeight ();

            final int countX = Math.max ( width, height );
            int positionX = -Math.max ( width, height );
            int positionY;
            for (int i = 0; i <= countX * 2; i++) {
                positionX += textWidth;
                for (int j = 0; j <= height; j++) {
                    positionY = textHeight * j;
                    if (j % 8 == 0) {
                        g.drawString ( pressText.get ( 0 ), positionX, positionY );
                        g.drawString ( pressText.get ( 1 ), positionX, positionY + textHeight );
                    }
                }
            }
            g.dispose ();
            bs = new ByteArrayOutputStream ();
            imOut = ImageIO.createImageOutputStream ( bs );
            ImageIO.write ( bufferedImage, suffix, imOut );

        } catch (Exception e) {
            e.printStackTrace ();
        }
        assert bs != null;
        return new ByteArrayInputStream ( bs.toByteArray () );
    }

    /**
     * 添加图片＋文字前置水印
     *
     * @param file    文件里流
     * @param name    水印名称
     * @param address 地址
     *
     * @return 文件流
     *
     * @throws Exception 异常
     */
    private InputStream newAddWaterMarkByText(MultipartFile file, String name, String address) throws Exception {
        ByteArrayOutputStream bs = null;
        ImageOutputStream imOut = null;
        try {
            // 检测图片是否被旋转
            int i = checkRotate ( file.getInputStream (), Objects.requireNonNull ( file.getContentType () ) );
            BufferedImage targetImage = ImageIO.read ( file.getInputStream () );
            // 获取正面图片
            targetImage = Rotate ( targetImage, i );
            //需要加水印的图片
            Graphics2D graphics = targetImage.createGraphics ();
            //开始加水印
            drawing ( address, name, targetImage, graphics );
            //回收垃圾
            graphics.dispose ();

            bs = new ByteArrayOutputStream ();
            imOut = ImageIO.createImageOutputStream ( bs );
            ImageIO.write ( targetImage, "jpg", imOut );

        } finally {
            if (imOut != null) {
                try {
                    imOut.close ();
                } catch (IOException e) {
                    e.printStackTrace ();
                }
            }
            if (bs != null) {
                try {
                    bs.close ();
                } catch (IOException e) {
                    e.printStackTrace ();
                }
            }
        }
        return new ByteArrayInputStream ( bs.toByteArray () );
    }

    /**
     * 解析本地字体
     *
     * @param fontFile
     * @param fontSize
     *
     * @return
     */
    private Font loadFont(InputStream fontFile, float fontSize) {
        try {
            Font dynamicFont = Font.createFont ( Font.TRUETYPE_FONT, fontFile );
            Font dynamicFontPt = dynamicFont.deriveFont ( fontSize );
            fontFile.close ();
            return dynamicFontPt;
        } catch (Exception e) {
            e.printStackTrace ();
            return new java.awt.Font ( "黑体", Font.PLAIN, 14 );
        }
    }

    /**
     * 对图片进行旋转
     *
     * @param src   被旋转图片
     * @param angel 旋转角度
     *
     * @return 旋转后的图片
     */
    private BufferedImage Rotate(BufferedImage src, int angel) {
        int src_width = src.getWidth ();
        int src_height = src.getHeight ();
        // 计算旋转后图片的尺寸
        Rectangle rect_des = CalcRotatedSize ( new Rectangle ( new Dimension ( src_width, src_height ) ), angel );
        BufferedImage res = new BufferedImage ( rect_des.width, rect_des.height, BufferedImage.TYPE_INT_RGB );
        Graphics2D g2 = res.createGraphics ();
        // 进行转换
        g2.translate ( (rect_des.width - src_width) / 2, (rect_des.height - src_height) / 2 );
        g2.rotate ( Math.toRadians ( angel ), src_width / 2, src_height / 2 );

        g2.drawImage ( src, null, null );
        return res;
    }

    /**
     * 计算旋转后的图片
     *
     * @param src   被旋转的图片
     * @param angel 旋转角度
     *
     * @return 旋转后的图片
     */
    private Rectangle CalcRotatedSize(Rectangle src, int angel) {
        // 如果旋转的角度大于90度做相应的转换
        if (angel >= 90) {
            if (angel / 90 % 2 == 1) {
                int temp = src.height;
                src.height = src.width;
                src.width = temp;
            }
            angel = angel % 90;
        }

        double r = Math.sqrt ( src.height * src.height + src.width * src.width ) / 2;
        double len = 2 * Math.sin ( Math.toRadians ( angel ) / 2 ) * r;
        double angel_alpha = (Math.PI - Math.toRadians ( angel )) / 2;
        double angel_dalta_width = Math.atan ( (double) src.height / src.width );
        double angel_dalta_height = Math.atan ( (double) src.width / src.height );

        int len_dalta_width = (int) (len * Math.cos ( Math.PI - angel_alpha - angel_dalta_width ));
        int len_dalta_height = (int) (len * Math.cos ( Math.PI - angel_alpha - angel_dalta_height ));
        int des_width = src.width + len_dalta_width * 2;
        int des_height = src.height + len_dalta_height * 2;
        return new Rectangle ( new Dimension ( des_width, des_height ) );
    }


    /**
     * 获取水印宽度
     *
     * @param graphics
     * @param font
     * @param text
     *
     * @return
     */
    private Integer getWidth(Graphics2D graphics, Font font, String text) {
        return graphics.getFontMetrics ( font ).stringWidth ( text );
    }

    /**
     * 获取水印的高度
     *
     * @param font
     * @param logoText
     * @param graphics
     *
     * @return
     */
    private float getHight(Graphics2D graphics, Font font, String logoText) {
        return font.getLineMetrics ( logoText, graphics.getFontRenderContext () ).getHeight ();
    }

    /**
     * 画水印
     *
     * @param address     地址信息
     * @param name        拍照片人姓名
     * @param targetImage 拍的照片
     * @param g           照片画布
     *
     * @throws IOException
     */
    private void drawing(String address, String name, BufferedImage targetImage, Graphics2D g) throws IOException {
        //竖线
        BufferedImage shuXian = ImageIO.read ( this.getClass ().getResourceAsStream ( "/excleTemplate/shuxian@2x.png" ) );
        g.drawImage ( shuXian, 50, 50, shuXian.getWidth (), shuXian.getHeight (), null );
        //logo
        BufferedImage logoImage = ImageIO.read ( this.getClass ().getResourceAsStream ( "/excleTemplate/logo@2x.png" ) );
        g.drawImage ( logoImage, 50 + 25, shuXian.getHeight () + (int) (logoImage.getHeight () * 0.2), null );
        //消除字体锯齿
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        //文字属性
        g.setColor ( Color.white );
        g.setFont ( new Font ( "Arial", Font.PLAIN, 60 ) );
        //获取当前时间
        LocalDateTime nowTime = LocalDateTime.now ();
        // 消除文字锯齿
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        //时间
        g.drawString ( nowTime.format ( DateTimeFormatter.ofPattern ( "HH:mm" ) ), 50 + 25, 94 );
        //日期
        g.setFont ( new Font ( "Arial", Font.PLAIN, 24 ) );
        // 消除文字锯齿
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        g.drawString ( nowTime.format ( DateTimeFormatter.ofPattern ( "yyyy.MM.dd" ) ), 50 + 25, 125 );
        //星期
        g.setFont ( new Font ( "黑体", Font.BOLD, 24 ) );
        String dayOfWeekCN = "{'MONDAY':'星期一','TUESDAY':'星期二','WEDNESDAY':'星期三','THURSDAY':'星期四','FRIDAY':'星期五','SATURDAY':'星期六','SUNDAY':'星期日'}";
        // 消除文字锯齿
        g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        g.drawString ( JSON.parseObject ( dayOfWeekCN ).getString ( nowTime.getDayOfWeek ().name () ), 50 + 35 + logoImage.getWidth (), 126 );
        //定位图标
        BufferedImage addressLogo = ImageIO.read ( this.getClass ().getResourceAsStream ( "/excleTemplate/dingwei@2x.png" ) );
        //头像图标
        BufferedImage userLogo = ImageIO.read ( this.getClass ().getResourceAsStream ( "/excleTemplate/touxiang@2x.png" ) );

        Font font = new Font ( "黑体", Font.PLAIN, 24 );
        g.setFont ( font );
        int width = getWidth ( g, font, address );
        float hight = getHight ( g, font, address );
        //右边空白
        int rightLen = 50;
        //左边空白
        int leftLen = 50;
        int x = targetImage.getWidth () - width - addressLogo.getWidth () - rightLen;
        if (x > leftLen) {
            // 消除画图锯齿
            g.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT );
            g.drawImage ( addressLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight, null );
            // 消除文字锯齿
            g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
            g.drawString ( address, x + addressLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () );

            width = getWidth ( g, font, name );
            hight = getHight ( g, font, name );
            x = targetImage.getWidth () - width - userLogo.getWidth () - rightLen;
            // 消除画图锯齿
            g.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT );
            g.drawImage ( userLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight - (int) (userLogo.getHeight () * 1.5), null );
            // 消除文字锯齿
            g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
            g.drawString ( name, x + userLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () - (int) (userLogo.getHeight () * 1.5) );
        } else {
            //单个字体的宽度，跟字体的大小的变化而变化（精度尽可能功能取正确）
            float oneTextLen = (width * 1.00000f) / address.length ();
            //设置一个分段的规则：此处保证 图片的左边 100像素和右边150像素为空白处; textLen：每行文本占用的长度
            int textLen = targetImage.getWidth () - (leftLen + rightLen) - addressLogo.getWidth ();
            //根据文本占用的长度得到单行基数
            int ruleNum = (int) (textLen / oneTextLen);
            if (Math.abs ( x ) < textLen) {

                String str_1 = address.substring ( 0, ruleNum );
                String str_2 = address.substring ( ruleNum );

                x = targetImage.getWidth () - getWidth ( g, font, str_1 ) - addressLogo.getWidth () - rightLen;
                // 消除画图锯齿
                g.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT );
                g.drawImage ( addressLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight - addressLogo.getHeight (), null );
                // 消除文字锯齿
                g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
                g.drawString ( str_1, x + addressLogo.getWidth () + 10, targetImage.getHeight () - (addressLogo.getHeight () * 2) );
                g.drawString ( str_2, x + addressLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () );

                width = getWidth ( g, font, name );
                hight = getHight ( g, font, name );
                x = targetImage.getWidth () - width - userLogo.getWidth () - rightLen;
                g.drawImage ( userLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight - (int) (userLogo.getHeight () * 2.5), null );
                // 消除文字锯齿
                g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
                g.drawString ( name, x + userLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () - (int) (userLogo.getHeight () * 2.5) );

            } else {
                String str_1 = address.substring ( 0, ruleNum );
                String str_2 = address.substring ( ruleNum, ruleNum * 2 );
                String str_3 = address.substring ( ruleNum * 2 );
                x = targetImage.getWidth () - getWidth ( g, font, str_1 ) - addressLogo.getWidth () - rightLen;
                // 消除画图锯齿
                g.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT );
                g.drawImage ( addressLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight - (addressLogo.getHeight () * 2), null );
                // 消除文字锯齿
                g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
                g.drawString ( str_1, x + addressLogo.getWidth () + 10, targetImage.getHeight () - (addressLogo.getHeight () * 3) );
                g.drawString ( str_2, x + addressLogo.getWidth () + 10, targetImage.getHeight () - (addressLogo.getHeight () * 2) );
                g.drawString ( str_3, x + addressLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () );

                width = getWidth ( g, font, name );
                hight = getHight ( g, font, name );
                x = targetImage.getWidth () - width - userLogo.getWidth () - rightLen;
                g.drawImage ( userLogo, x, targetImage.getHeight () - addressLogo.getHeight () - (int) hight - (int) (userLogo.getHeight () * 3.5), null );
                // 消除文字锯齿
                g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
                g.drawString ( name, x + userLogo.getWidth () + 10, targetImage.getHeight () - addressLogo.getHeight () - (int) (userLogo.getHeight () * 3.5) );

            }
        }
    }

    /**
     * 读取照片被系统旋转过角度
     *
     * @param ins
     *
     * @return
     */
    private int checkRotate(InputStream ins, String fileType) throws Exception {
        Metadata metadata = ImageMetadataReader.readMetadata ( ins, -1, fileType.contains ( "png" ) ? FileType.Png : FileType.Jpeg );
        for (Directory directory : metadata.getDirectories ()) {
            if ("Exif IFD0".equals ( directory.getName () )) {
                for (Tag tag : directory.getTags ()) {
                    log.info ( "Tag Info:{}", tag.toString () );
                    Pattern pattern = Pattern.compile ( "(\\d+0)" );
                    Matcher matcher = pattern.matcher ( tag.toString () );
                    if (matcher.find ()) {
                        if (tag.toString ().contains ( "Right" )) {
                            return Integer.parseInt ( matcher.group () );
                        }
                        if (tag.toString ().contains ( "left" )) {
                            return 360 - Integer.parseInt ( matcher.group () );
                        }
                    }
                }
            }
        }
        return 0;
    }
}
